/****************************************************************
 ** Product  :   HP Subscriber Network Application Policy
 ** Module   :   gr-manager
 ** Date: Apr 10, 2015               
 ** Author: Hua Zhen
 ** (C) Copyright 2015, Hewlett-Packard Company, All Rights Reserved.
 ** This software is the proprietary information of HP, Inc.  
 ** Use is subject to license terms.
 *****************************************************************
 ** Description:   
 **    Sync DB and wait the replication finished
 **    
 ****************************************************************/
package com.hp.snap.gr.action.impl;

import com.google.gson.Gson;
import com.hp.snap.gr.db.voltdb.ControlDRAgent;
import com.hp.snap.gr.db.voltdb.JSONHttpVoltDBClientFactory;
import com.hp.snap.gr.db.voltdb.VoltDBClientConfig;
import com.hp.snap.gr.db.voltdb.sp.StatisticsDRResponses;
import com.hp.snap.gr.db.voltdb.sp.VoltDBTable;
import com.hp.snap.gr.exception.ActionException;
import com.hp.snap.gr.exception.DatabaseException;
import com.hp.snap.gr.model.Database;
import com.hp.snap.gr.model.RESTfulMethod;
import com.hp.snap.gr.model.jaxb.ActionType;
import com.hp.snap.gr.model.jaxb.GeneralActionType;
import com.hp.snap.gr.model.jaxb.ParameterType;
import com.hp.snap.gr.utils.Constants;
import com.hp.snap.gr.utils.GRMConfigManager;
import com.hp.snap.gr.utils.RESTfulUtils;
import com.hp.snap.gr.utils.Utils;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 
 * 
 * Call the system store procedure "@Statistics" to get Sync database status, if the replication sync not
 * finished, it will wait 5 second to check sync status again, till the replication finished.
 * 
 * There is a parameter can be configured in the action, DB_INSTANCE_NAME_LIST
 * DB_INSTANCE_NAME_LIST: the VoltDB instance name list, for multiple instance, split by ",".
 * 
 * <P>
 * Version: 3.3 <br>
 * Author: Hua zhen Date: Apr 10, 2015
 * 
 * </P>
 * 
 **/
public class LocalDBSyncFinishedActionHandler extends AbstractActionHandler {

    private static Logger logger = LoggerFactory.getLogger(LocalDBSyncFinishedActionHandler.class);

    @Override
    public void execute(ActionType action, Map<String, Object> actionPlanInputs,
                        Map<String, Object> actionPlanOutputs) throws ActionException {

        GeneralActionType actionType = (GeneralActionType) action;
        List<ParameterType> parameterTypes = actionType.getParameter();
        Map<String, String> parameterMap = Utils.contructParamMap(parameterTypes);

        /**
         * get the db instance name list, which should be configured in: /SNAP/GR/<DEPLOY_NAME>/VOLTDB/<DB_INSTANCE_NAME>, e.g.
         * /SNAP/GR/Site_A1/VOLTDB/session_db]
         */
        String dbInstName = parameterMap.get("DB_INSTANCE_NAME_LIST");
        if (StringUtils.isEmpty(dbInstName)) {
            throw new ActionException("The parameter [DB_INSTANCE_NAME_LIST] should be configured!");
        }

        String[] databaseNames = dbInstName.split(",");

        // get remote site name
        String remoteSiteName = Utils.getCorrespondSiteName(GRMConfigManager.getSiteName());

        // get remote site database profiles
        Map<String, Database> remoteDatabases = RESTfulUtils.getVoltDBDatabases(remoteSiteName);

        // get local site database profiles
        Map<String, Database> localDatabases = RESTfulUtils.getVoltDBDatabases(GRMConfigManager.getSiteName());

        if (remoteDatabases.isEmpty()) {
            throw new ActionException("Cannot get remote site DB profiles!");
        }

        if (localDatabases.isEmpty()) {
            throw new ActionException("Cannot get local site DB profiles!");
        }

        // pause remote databases
        List<Database> pausedDBs = new ArrayList<Database>();
//        for (String databaseName : databaseNames) {
//            Database database = remoteDatabases.get(databaseName);
//            if (database == null) {
//                throw new ActionException("DB instance [" + databaseName + "] cannot be found!");
//            }
//
//            try {
//                boolean result = pauseRemoteDB(remoteSiteName, database);
//
//                if (result) {
//                    pausedDBs.add(database);
//                }
//
//            } catch (ActionException e) {
//                logger.error("Pause DB failed!", e);
//                logger.info("Trying to resume the previous paused ones...");
//
//                resumeRemoteDB(pausedDBs, remoteSiteName);
//
//                throw e;
//            }
//
//        }
//
//        logger.info("All DBs are paused successfully!");

        // pause remote databases
        List<Database> localDBs = new ArrayList<Database>();
        for (String databaseName : databaseNames) {
            Database database = localDatabases.get(databaseName);
            if (database == null) {
                throw new ActionException("DB instance [" + databaseName + "] cannot be found!");
            }
            localDBs.add(database);
        }

        if (logger.isTraceEnabled()) {
            logger.trace("remote DBs={}, local DBs={}", new Gson().toJsonTree(pausedDBs, List.class), new Gson().toJsonTree(localDBs, List.class));
        }
        // wait the sync finished
        // Map<String, List<VoltDBClientConfig>> dbClientConfigs = ControlDRAgent.getVoltDBClientConfig(pausedDBs, remoteSiteName);
        Map<String, List<VoltDBClientConfig>> dbClientConfigs = ControlDRAgent.getVoltDBClientConfig(localDBs, GRMConfigManager.getSiteName());

        List<Database> unFinishedDBs = checkSyncStatus(dbClientConfigs, localDBs);

        while (!unFinishedDBs.isEmpty()) {
            try {

                logger.info("Waiting 5 seconds for the DB sync finished....");

                Thread.sleep(5000);
            } catch (InterruptedException e) {
                throw new ActionException("Wait the un-finished DB sync failed!", e);
            }
            unFinishedDBs = checkSyncStatus(dbClientConfigs, localDBs);
        }

        logger.info("All the DB instances have been synchronized!");

    }

    private List<Database> checkSyncStatus(Map<String, List<VoltDBClientConfig>> dbClientConfigs,
                                           List<Database> pausedDBs) throws DatabaseException {
        List<Object> params = Arrays.asList("DR", (Object) 1);
        List<Database> unfinishDBs = new ArrayList<Database>();
        for (Database database : pausedDBs) {

            List<VoltDBClientConfig> configs = dbClientConfigs.get(database.getName());
            JSONHttpVoltDBClientFactory factory = new JSONHttpVoltDBClientFactory(configs);

            JSONObject response = factory.invokeStoredProcedure("@Statistics", params);

            JSONObject streams = StatisticsDRResponses.getReplicationStreams(response);
            JSONObject snapshots = StatisticsDRResponses.getReplicationSnapshot(response);

            int notFinishedCounts = StatisticsDRResponses.getSyncNotFinishedCount(streams);
            VoltDBTable snapshotTable = StatisticsDRResponses.getReplicatingSnapshot(snapshots);
            int notFinishedSnapShotCounts = snapshotTable.getRows().size();

            if (notFinishedCounts > 0 || notFinishedSnapShotCounts > 0) {

                logger.info("The sync for DB [{}] is not finished, Un-Finished Transaction: {}, Un-Finished Snapshot: {}",
                        new Object[]{database.getName(), notFinishedCounts, notFinishedSnapShotCounts});

                unfinishDBs.add(database);
            } else {
                logger.info("The sync for DB [{}] has finished.", database.getName());
            }

        }
        return unfinishDBs;
    }

    private void resumeRemoteDB(List<Database> pausedDBs, String remoteSiteName) throws ActionException {
        if (pausedDBs == null || pausedDBs.isEmpty()) {
            return;
        }

        for (Database database : pausedDBs) {
            String resumeURI = "/mgmt/databases/" + database.getId() + "/resume";

            int resultCode = RESTfulUtils.invokeVEMRESTAPIReturnResultCode(remoteSiteName, resumeURI, RESTfulMethod.PUT);

            if (resultCode == Constants.RESTFUL_SUCCESS_STATUS_CODE) {
                logger.info("Resume the DB Instance successful, Name: {}, ID: {}", database.getName(), database.getId());
            } else {
                throw new ActionException("Cannot resume the DB [" + database.getName() + "]!");
            }
        }
    }

    private boolean pauseRemoteDB(String remoteSiteName, Database database) throws ActionException {
        String status = database.getStatus();
        boolean replica = database.isReplica();

        if (Constants.VDB_STATUS_ONLINE.equals(status) && !replica) {
            String pauseURI = "/mgmt/databases/" + database.getId() + "/pause";

            int resultCode = RESTfulUtils.invokeVEMRESTAPIReturnResultCode(remoteSiteName, pauseURI, RESTfulMethod.PUT);

            if (resultCode == Constants.RESTFUL_PUT_ASYNC_SUCCESS_STATUS_CODE) {
                logger.info("Pause the DB Instance successful, Name: {}, ID: {}", database.getName(), database.getId());
                return true;
            } else {
                throw new ActionException("Cannot pause the DB [" + database.getName() + "], return the result code is " + resultCode + "!");
            }

        } else if (Constants.VDB_STATUS_PAUSED.equals(status)) {

            logger.info("The DB Instance has already being paused, Name: {}, ID: {}", database.getName(), database.getId());

            return true;

        } else {
            logger.warn("The database is in status [{}] and the mode is [{}], we cannot pause it!", status,
                    replica ? Constants.VDB_MODE_REPLICA : Constants.VDB_MODE_MASTER);
        }

        return false;
    }

}
